package drds.server.net;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.concurrent.atomic.AtomicBoolean;

import drds.server.util.CurrentTimeMillis;

/**
 * 
 * @author 曹正辉<330937205@qq.com>
 * 
 */
public class ReadWrite extends IReadWrite {
	private SelectionKey selectionKey;
	private static final int OP_NOT_READ = ~SelectionKey.OP_READ;
	private static final int OP_NOT_WRITE = ~SelectionKey.OP_WRITE;
	private final SessionContext$ReadWrite sessionContext;
	private final SocketChannel socketChannel;
	private final AtomicBoolean writing = new AtomicBoolean(false);

	public ReadWrite(SessionContext$ReadWrite sessionContext) {
		this.sessionContext = sessionContext;
		this.socketChannel = (SocketChannel) sessionContext.socketChannel;
	}

	public void registerReadEvent(Selector selector) throws IOException {
		try {
			selectionKey = socketChannel.register(selector, SelectionKey.OP_READ, sessionContext);
		} finally {
			if (sessionContext.isClosed.get()) {
				clearSelectionKey();
			}
		}
	}

	public void write() {

		if (!writing.compareAndSet(false, true)) {
			return;
		}

		try {
			boolean noMoreData = writeData();
			writing.set(false);
			if (noMoreData && sessionContext.writeByteBufferQueue.isEmpty()) {
				if ((selectionKey.isValid() && (selectionKey.interestOps() & SelectionKey.OP_WRITE) != 0)) {
					disableWrite();
				}

			} else {

				if ((selectionKey.isValid() && (selectionKey.interestOps() & SelectionKey.OP_WRITE) == 0)) {
					enableWrite(false);
				}
			}

		} catch (IOException e) {
			if (SessionContext$ReadWrite.LOGGER.isDebugEnabled()) {
				SessionContext$ReadWrite.LOGGER.debug("caught err:", e);
			}
			sessionContext.close("err:" + e);
		}

	}

	private boolean writeData() throws IOException {

		int writtedSize = 0;
		ByteBuffer byteBuffer = sessionContext.writeByteBuffer;
		if (byteBuffer != null) {
			while (byteBuffer.hasRemaining()) {
				writtedSize = socketChannel.write(byteBuffer);
				if (writtedSize > 0) {
					sessionContext.netOutBytesSize += writtedSize;
					sessionContext.processor.addNetOutBytes(writtedSize);
					sessionContext.lastWriteTime = CurrentTimeMillis.currentTimeMillis();
				} else {
					break;
				}
			}

			if (byteBuffer.hasRemaining()) {
				sessionContext.writeAttempts++;
				return false;
			} else {
				sessionContext.writeByteBuffer = null;
				sessionContext.recycle(byteBuffer);
			}
		}
		while ((byteBuffer = sessionContext.writeByteBufferQueue.poll()) != null) {
			if (byteBuffer.limit() == 0) {
				sessionContext.recycle(byteBuffer);
				sessionContext.close("quit send");
				return true;
			}

			byteBuffer.flip();
			while (byteBuffer.hasRemaining()) {
				writtedSize = socketChannel.write(byteBuffer);
				if (writtedSize > 0) {
					sessionContext.lastWriteTime = CurrentTimeMillis.currentTimeMillis();
					sessionContext.netOutBytesSize += writtedSize;
					sessionContext.processor.addNetOutBytes(writtedSize);
					sessionContext.lastWriteTime = CurrentTimeMillis.currentTimeMillis();
				} else {
					break;
				}
			}
			if (byteBuffer.hasRemaining()) {
				sessionContext.writeByteBuffer = byteBuffer;
				sessionContext.writeAttempts++;
				return false;
			} else {
				sessionContext.recycle(byteBuffer);
			}
		}
		return true;
	}

	private void disableWrite() {
		try {
			SelectionKey selectionKey = this.selectionKey;
			selectionKey.interestOps(selectionKey.interestOps() & OP_NOT_WRITE);
		} catch (Exception e) {
			SessionContext$ReadWrite.LOGGER.error("can't disable write " + e + " con " + sessionContext);
		}

	}

	private void enableWrite(boolean wakeup) {
		boolean needWakeup = false;
		try {
			SelectionKey selectionKey = this.selectionKey;
			selectionKey.interestOps(selectionKey.interestOps() | SelectionKey.OP_WRITE);
			needWakeup = true;
		} catch (Exception e) {
			SessionContext$ReadWrite.LOGGER.error("can't enable write " + e);

		}
		if (needWakeup && wakeup) {
			selectionKey.selector().wakeup();
		}
	}

	public void disableRead() {

		SelectionKey selectionKey = this.selectionKey;
		selectionKey.interestOps(selectionKey.interestOps() & OP_NOT_READ);
	}

	public void enableRead() {

		boolean needWakeup = false;
		try {
			SelectionKey selectionKey = this.selectionKey;
			selectionKey.interestOps(selectionKey.interestOps() | SelectionKey.OP_READ);
			needWakeup = true;
		} catch (Exception e) {
			SessionContext$ReadWrite.LOGGER.error("enable read fail " + e);
		}
		if (needWakeup) {
			selectionKey.selector().wakeup();
		}
	}

	private void clearSelectionKey() {
		try {
			SelectionKey selectionKey = this.selectionKey;
			if (selectionKey != null && selectionKey.isValid()) {
				selectionKey.attach(null);
				selectionKey.cancel();
			}
		} catch (Exception e) {
			SessionContext$ReadWrite.LOGGER.error("clear selector keys err:" + e);
		}
	}

	@Override
	public void read() throws IOException {
		ByteBuffer byteBuffer = sessionContext.readByteBuffer;
		if (byteBuffer == null) {
			byteBuffer = sessionContext.processor.getByteBufferPool().allocate(sessionContext.processor.getByteBufferPool().getBlockSize());
			sessionContext.readByteBuffer = byteBuffer;
		}
		int readDataSize = socketChannel.read(byteBuffer);
		sessionContext.onReadData(readDataSize);
	}

}
